Livrable 4 : Modélisation numérique

Quentin Mille
Simon Côte
Julien Grenet
Hassan Ayoubi
Raphaël Linard
Raphaël Haustant

Sommaire

  1. Rappel du projet
  2. Introduction
  3. Chaine de transmission
  4. Code
    4.1 Phase de transmission
    4.2 Phase de réception
    4.3 Système de détection d’erreurs
  5. POC
  6. Bonus
  7. Conclusion

1. Rappel du projet :

L’agence AIL3C (Agence indépendante de lutte contre la cybercriminalité) a réussi à localiser une base d’opération de cybercriminels dangereux. Lors d’une
opération d’infiltration menée par l’agent K57, il réussit à capturer des informations sensibles dans la salle des serveurs. Malheureusement, il se retrouve
à devoir se cacher dans une pièce équipée de micros et de murs empêchant toute communication extérieure. Il finit par être capturé.

Objectif : Nous devons concevoir et soumettre à l’équipe R&D une solution permettant la communication sans être repéré.

2. Introduction :

Ayant désormais la vision complète de la chaîne de transmission pour réaliser la communication, vous devez mettre en place un prototype pour faire la démonstration de la viabilité de votre solution.

3. Chaine de transmission :

On a la partie verte pour la phase d’emission / de transmission et la partie bleue pour la partie.

Explication détaillé de la chaine de transmission:
1. Message
L’agent réfléchit à un message simple et consit pour être le plus discret possible
2. PAD
L’agent écrit à l’aide du pavé numérique du pad et envoie le message. Ce message est converti en binaire.
3. Encodage Manchester
Le message binaire est encodé en Manchester.
Un 0 se transforme en 01 et un 1 se transforme en 10.
Le codage Manchester présente de nombreux avantages :

4. Modulation ASK / FSK

5. Fichier audio
La modulation FSK / ASK donne un fichier audio qui est ensuite émit par les hauts parleurs du pad sous forme d’un signal ultrason, non audible pour l’être humain mais transmissible et récupérable

6. Microphone
Le microphone capte l’audio. Il le transfert en signal analogique électrique

7.Récepteur
On suppose que la réception du signal se fait directement dans le réseau de la base

7. Démodulation ASK / FSK
Le fichier audio est démodulé pour obtenir des bits.

8. Décodage Manchester inversé
Les bits sont décodés en Manchester pour retouver leur bits d’origine.

9. Fichier audio / Message
On peut lire le fichier audio ou juste convertir ce signal suite binaire en ASCII pour lire le message

10.Exemple d’un message
Exemple avec la lettre H :

  1. Caractère ASCII “H” : valeur décimale : 72
  2. Message converti en binaire : H -> 0100 1000
  3. Codage Manchester : 01 10 01 01 10 01 01 01
  4. Modulation FSK : f1 f2 f1 f2 f2 f1 f2 f1


7. Mirco : Ce signal est transmit par les hauts - parleurs du pad vers le micro
8. Recepteur (Antenne) : L’antenne récupère ce signal
9. Démodulation : La démodulation donne donc : 01 10 01 01 10 01 01 01
10. Décodage : En décodant on obtient : 0100 1000
11. Binaire converti en message (ASCII) : 0100 1000 donne donc 72 en décimal soit la lettre “H”
12. Réception (Agent extérieur) : L’agent reçoit la lettre “H”

4. Code :

Pour notre code on optera pour une structure dans laquelle on définit toutes les fonctions nécessaires et ensuite on exécutera le code final de notre POC en utilisant ces fonctions.

# Importation des bibliothèques nécessaires 
import numpy as np # Import de la bibliothèque numpy
import matplotlib.pyplot as plt # Import de la bibliothèque matplotlib
from scipy.signal import periodogram # Import de la fonction periodogram du module signal de scipy

4.1. Phase de transmission

# 1. Conversion du texte en binaire (en une liste de bits)
def texte_vers_binaire(message_1):
    # '08b' -> convertit en chaine de 8 bits 
    # ord(char) -> convertit chaque caratère en ASCII
    message_binaire_1 = [int(bit) for char in message_1 for bit in format(ord(char), '08b')]
    return message_binaire_1

# 2. Encodage Manchester (prend une liste de bits et retourne une liste de bits encodés)
# Un bit '0' -> '01'
# Un bit '1' -> '10'
def encodage_manchester(message_binaire_1): 
    encodage_M = []
    for bit in message_binaire_1:
        if bit == 1:
            encodage_M.extend([1, 0])  # Manchester pour 1
        else:
            encodage_M.extend([0, 1])  # Manchester pour 0
    return encodage_M 

# Initialisation des variables
Fe = 52000 # Fréquence d'échantillonnage
baud = 300 # Baud
Ap = 1 # Amplitude porteuse
Fp = 2000 # Fréquence porteuse
Fp1 = 2000 # Fréquence porteuse 1
Fp2 = 5000 # Fréquence porteuse 2
Ns = int(Fe / baud) # Nombre de symboles par bit

# 3. Modulation ASK 
def modulation_ASK(encodage_M):
    # Initialisation des variables
    # Génére un message qui duplique chaque bit du message encodé Ns fois pour synchroniser le signal binaire avec la porteuse 
    M_duplique = np.repeat([int(bit) for bit in encodage_M], Ns)
    N = len(M_duplique) # Taille de M_duplique
    D = N / Fe # Durée
    t = np.arange(0, D, 1 / Fe) # Vecteur temps

    # Générer la porteuse ASK
    Porteuse = Ap * np.sin(2 * np.pi * Fp * t)
    
    # Signal ASK
    ASK = M_duplique * Porteuse
    
    return t, M_duplique, Porteuse, ASK

4.2. Phase de réception

# 1. Démodulation ASK
def demodulation_ASK(t, ASK, encodage_M):
# Génére un message qui duplique chaque bit du message encodé Ns fois pour synchroniser le signal binaire avec la porteuse 
    M_duplique = np.repeat([int(bit) for bit in encodage_M], Ns)
    N = len(M_duplique) # Taille de M_duplique
    
    # Recréer la porteuse
    Porteuse = np.sin(2 * np.pi * Fp * t)
    
    # Produit entre le signal modulé ASK et la porteuse
    Produit = ASK * Porteuse
    
    # Intégration sur chaque période de symbole (avec la méthode des Trapèzes)
    Res= [] # Résultat de l'intégration                         

    i=0
    for i in range(0,N,Ns):
        Res.append (np.trapz(Produit[i:i+Ns],t[i:i+Ns])) 

    # Si Res > 0 donc on a reçu un 1 (True) sinon un 0 (False)
    message_demodule = np.array(Res) > 0 # Renvoie True (si > 0) ou False sinon

    # Decodage du signal démodulé
    message_demodule_ASK = [] # Résultat du comparateur

    for ii in range (0,len(message_demodule)):
        if message_demodule [ii] == True:
            message_demodule_ASK.extend([int(1)]) # Si l'intégrale est positive, c'est un 1
        else:
            message_demodule_ASK.extend([int(0)]) # Si l'intégrale est négative ou nulle, c'est un 0
            
    return message_demodule_ASK

# 2. Décodage Manchester inversé
def decodage_manchester(message_binaire_2):
    # Décodage Manchester (prend une liste de bits encodés et retourne une liste de bits)
    # Reconstruction du message binaire original
    binaire_texte = []
    for i in range(0, len(message_binaire_2), 2):
        if message_binaire_2[i:i+2] == [1, 0]: # Bloc "10" correspond à 1
            binaire_texte.append(1)
        elif message_binaire_2[i:i+2] == [0, 1]: # Bloc "01" correspond à 0
            binaire_texte.append(0)
    return binaire_texte

# 3. Binaire vers message final 
def binaire_vers_texte(binaire_texte):
    # Conversion d'une liste de bits en texte (chaîne de caractères)
    texte = ''
    for i in range(0, len(binaire_texte), 8):
        # Convertit chaque groupe de 8 bits en caractère ASCII
        byte = binaire_texte[i:i+8]
        texte += chr(int(''.join(map(str, byte)), 2)) # Conversion des bits en nombre entier puis en caractère
    return texte

4.3. Système de détection d’erreurs

## 5. Détection d'erreurs 
def detection_erreur(encodage, message_demodule):
    Erreur = []  # Initialiser une liste pour stocker les erreurs
    for i in range(len(encodage)):
        if encodage[i] != message_demodule[i]:
            Erreur.append(1)  # Erreur détectée
        else:
            Erreur.append(0)  # Pas d'erreur
    # Compter le nombre d'erreurs
    nombre_erreurs = sum(Erreur)
    print("Erreur de réception :\n", Erreur)
    print(f"Nombre d'erreurs : {nombre_erreurs}")
    return Erreur

5. POC

# Exécution du code 
print("Message original :", texte_entree)

message_binaire_1 = texte_vers_binaire(texte_entree)
print("Message en binaire :", message_binaire_1)

message_encode = encodage_manchester(message_binaire_1)
print("Messag encodé en Manchester :", message_encode)

###------- Modulation ASK ----------###
t, M_duplique, Porteuse, ASK = modulation_ASK(message_encode)

# Affichage des graphiques
plt.figure(figsize=(10, 8))
plt.subplot(3, 1, 1)
plt.plot(t, M_duplique, drawstyle='steps-post')
plt.title('Message Encodé en Manchester')
plt.xlabel('Temps (s)')
plt.ylabel('Amplitude')
plt.grid()

plt.subplot(3, 1, 2)
plt.xlim (0,0.01)
plt.plot(t, Porteuse)
plt.title('Porteuse P(t)')
plt.xlabel('Temps (s)')
plt.ylabel('Amplitude')
plt.grid()

plt.subplot(3, 1, 3)
plt.xlim (0,0.1)
plt.plot(t, ASK)
plt.title('Signal Modulé ASK(t)')
plt.xlabel('Temps (s)')
plt.ylabel('Amplitude')
plt.grid()

plt.tight_layout()
plt.show()

###------- Modulation FSK ----------###
t, M_duplique, P1, P2, FSK = modulation_FSK(message_encode)

# Affichage des graphiques
plt.figure(figsize=(10, 8))
plt.subplot(3, 1, 1)
plt.plot(t, M_duplique, drawstyle='steps-post')
plt.title('Message Encodé en Manchester')
plt.xlabel('Temps (s)')
plt.ylabel('Amplitude')
plt.grid()

plt.subplot(3, 1, 2)
plt.xlim (0,0.01)
plt.plot(t, P1)
plt.title('Porteuse P(t)')
plt.xlabel('Temps (s)')
plt.ylabel('Amplitude')
plt.grid()

plt.subplot(3, 1, 2)
plt.xlim (0,0.01)
plt.plot(t, P2)
plt.title('Porteuse P(t)')
plt.xlabel('Temps (s)')
plt.ylabel('Amplitude')
plt.grid()

plt.subplot(3, 1, 3)
plt.xlim (0,0.1)
plt.plot(t, FSK)
plt.title('Signal Modulé ASK(t)')
plt.xlabel('Temps (s)')
plt.ylabel('Amplitude')
plt.grid()

plt.tight_layout()
plt.show()

# Démodulation FSK
message_demodule = demodulation_FSK(t, FSK)
print("Message démodulé en codage manchester : ",message_demodule)
Erreur = detection_erreur(message_encode, message_demodule)

# Démodulation ASK
message_demodule1 = demodulation_ASK(t, ASK)
print("Message démodulé en codage manchester : ",message_demodule1)
Erreur1 = detection_erreur(message_encode, message_demodule1)

# Décodage FSK
message_decode = decodage_manchester(message_demodule)
print("Message décodé en binaire :", message_decode)

message_final = binaire_vers_texte(message_decode)
print("Message final décodé :", message_final)

# Décodage ASK
message_decode1 = decodage_manchester(message_demodule1)
print("Message décodé en binaire :", message_decode)

message_final1 = binaire_vers_texte(message_decode1)
print("Message final décodé :", message_final1)

6. Bonus (Implémenter que 2 fonctionnalités parmi les 6)

  1. L’envoi et la reconstitution d’un message de type son
from scipy.io.wavfile import write
import sounddevice as sd
from scipy.signal import periodogram 
# Fonction pour sauvegarder le signal audio
def save_audio_signal(signal, filename, sample_rate):
    signal_np = np.array(signal)  # Convertir en tableau NumPy
    write(filename, sample_rate, signal_np.astype(np.float32))

# Fonction pour jouer un signal audio
def play_audio_signal(signal, sample_rate):
    signal_np = np.array(signal)  # Convertir en tableau NumPy
    sd.play(signal_np, sample_rate)
    sd.wait()
  1. La gestion de la réception d’un signal avec bruit (en cours avec un filtre passe-haut)
  2. L’utilisation d’une liaison half duplex (accusé de réception)
  3. un CRC ou un code de correction d’erreur
  4. La simulation entre 2 PC (l’un pour la génération du son correspondant au message à envoyer, l’autre pour la réception et la reconstruction du message)
  5. La mise en œuvre d’un second type de modulation/démodulation (en cours avec FSK)

Code en vrac (temporaire)

# Filtre Passe Haut

f1 = 2000                              # fréquence du signal S1
f2 = 5000
f3= 10000
f4 = 26000                             # fréquence du signal S2
f5 = 25000
f6 = 20000

# S1 et S2 et S=S1+S2
S1 = Ap*np.sin(2*np.pi*f1*t)          # création d'une sinusoïde de fréquence f1
S2 = Ap*np.sin(2*np.pi*f2*t)            # création d'une sinusoïde de fréquence f2
S3 = Ap*np.sin(2*np.pi*f3*t)
S4 = Ap*np.sin(2*np.pi*f4*t)
S5 = Ap*np.sin(2*np.pi*f5*t)
S6 = Ap*np.sin(2*np.pi*f6*t)
S = S1 + S2 + S3 + S4 + S5 + S6

# calcul de la transformée de Fourier avec la fonction periodogram

f,FFT = periodogram(S,Fe)           # f: vecteur des fréquences et FFT:la transformée de Fourier du signal S=S1+S2

# Affichage du signal------------------------------------------------

plt.plot(t,S)                       # affichage via la fonction plot de Matplotlib
plt.xlabel('Temps (s)')             # définition de l'axe des abscisses
plt.ylabel('Amplitude') # définition de l'axe des ordonnées
plt.title ('Signal S',fontsize=14)
plt.grid()
plt.show()                          # affichage des courbes


# Affichage du de la transformée de Fourier FFT du signal S=S1+S2-----------------

plt.plot(f,FFT)                     # affichage via la fonction plot de Matplotlib
plt.xlabel('Fréquence (Hz)')        # définition de l'axe des abscisses
plt.ylabel('Module')                # définition de l'axe des ordonnées
plt.xlim(0, 28000)
plt.grid()
plt.title ('Module de la Transformée de Fourier du signal S=S1+S2',fontsize=14)
plt.show()              

# définir la fréquence de coupure fc du filtre à 75 Hz

fc = 25000

# on définit une variable qui reçoit le signal filtré de la même taille que la transformée de Fourier (FFT)

FFT_filtre = FFT

# on réalise un filre passe haut comme suit :

for i in range(len(f)):
    if f[i] < fc: # on coupe toutes les fréquences < 75 Hz 
        FFT_filtre[i] = 0.0
        
print(f)


# on calcule la transfomrée de Fourier inverse du signal après filtrage en utilsant la fonction ifft de Python
# la FFT inverse permet de revenir dans l'espace temporal (espace fréquentiel -> espace temps)

FFT_inverse = np.fft.ifft(FFT_filtre)
# attention le résulat est un nombre complexe
# on ne doit donc représenter que la partie réelle, imaginaire ou le module (réél^2 + imag^2)

plt.plot(f,FFT_filtre)                # affichage via la fonction plot de Matplotlib
plt.xlabel('Fréquence (Hz)')          # définition de l'axe des abscisses
plt.ylabel('Module')                  # définition de l'axe des ordonnées
plt.xlim(0, 28000)
plt.grid()
plt.title ('Module de la Transformée de Fourier du signal filtré s(t)',fontsize=14)
plt.show()  

plt.plot(np.real(FFT_inverse))         # affichage via la fonction plot de Matplotlib de la partie réelle
plt.ylabel('Amplitude')                # définition de l'axe des ordonnées
plt.xlim(0, 200)
plt.grid()
plt.title ('Le signal obtenu après filtrage',fontsize=14)
plt.show()